/*
 * I2C support for AT91SAM7S
 *
 * by Zik Saleeba 2008-01-22
 */

#include <string.h>

#include "ffox.h"
#include "twi.h"
#include "twid.h"
#include "i2c.h"
#include "AT91SAM7X256.h"
#include "lib_AT91SAM7X256.h"
#include "dbgprintf.h"

#define I2C_SPEED 100000
#define I2C_TIMEOUT (2000 / portTICK_RATE_MS)


/* the ISR can cause a context switch so is declared naked. */
void I2C_IsrWrapper(void) __attribute__ ((naked));
void I2C_IsrHandler(void);


/* module variables */
xSemaphoreHandle I2CLock;
xSemaphoreHandle I2CDone;
int I2CError = I2C_ERROR_OK;

/* TWI driver instance */
static Twid twid;
static AsyncTwi asynctwi;


/* local prototypes */
static void I2C_Callback(void *param);


/* 
 * NAME:        I2C_Init
 * ACTION:      Initialise the I2C peripheral 
 */
 
void I2C_Init()
{
    /* initialise the hardware */
    TWI_Configure(AT91C_BASE_TWI, I2C_SPEED, MCK);

    /* initialise the driver */
    TWID_Initialize(&twid, AT91C_BASE_TWI);
    
    /* enable the interrupts */
    AT91F_AIC_ConfigureIt(AT91C_BASE_AIC, AT91C_ID_TWI, 0, AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, &I2C_IsrWrapper);
    //TWI_DisableIt(AT91C_BASE_TWI, AT91C_TWI_TXCOMP | AT91C_TWI_RXRDY | AT91C_TWI_TXRDY | AT91C_TWI_NACK);
    //TWI_EnableIt(AT91C_BASE_TWI, AT91C_TWI_TXCOMP | AT91C_TWI_RXRDY | AT91C_TWI_TXRDY | AT91C_TWI_NACK);
    AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_TWI);

    /* create the semaphore which will be used to control access to the hardware */
    I2CLock = xSemaphoreCreateMutex();

    /* create the semaphore which will be used to wake the calling process up */
    vSemaphoreCreateBinary(I2CDone);
    xSemaphoreTake(I2CDone, 0);
    
    memset(&asynctwi, '\0', sizeof(asynctwi));
    asynctwi.callback = &I2C_Callback;
}


/* 
 * NAME:        I2C_Transfer
 * ACTION:      transmit some bytes and then receive some bytes via I2C 
 * PARAMETERS:  unsigned char Address - the I2C address to use
 *              const unsigned char *TxBuf - data to transmit
 *              int TxBytes - how many bytes to transmit
 *              unsigned char *RxBuf - received data
 *              int RxBytes - size of data to receive
 * RETURNS:     I2C_ERROR_OK on success, I2C_ERROR_* on error
 */
 
int I2C_Transfer(unsigned char Address, const unsigned char *TxBuf, int TxBytes, unsigned char *RxBuf, int RxBytes)
{
    int Result = I2C_ERROR_OK;
    
    //dbgprintf("entering I2C_Transfer(%x,%lx,%d,%lx,%d)\r\n", Address, (unsigned long)TxBuf, TxBytes, (unsigned long)RxBuf, RxBytes);

    /* lock access to the I2C hardware */
    if (xSemaphoreTake(I2CLock, I2C_TIMEOUT) != pdTRUE)
    {
        dbgprintf("can't get I2CLock in I2C_Transfer: error code %d\r\n", Result);
        return I2C_ERROR_TIMEOUT;
    }
    
    /* send data */
    if (TxBytes > 0)
    {
        //dbgprintf("I2C sending %d bytes\r\n", TxBytes);

        if (TWID_Write(&twid, Address, 0, 0, (unsigned char *)TxBuf, TxBytes, &asynctwi))
            Result = I2C_ERROR_BUSY;

        // wait for transmit complete
        //dbgprintf("waiting for transmit complete\r\n");
        if (xSemaphoreTake(I2CDone, I2C_TIMEOUT) != pdTRUE)
        {
            dbgprintf("can't get I2CDone in I2C_Transfer\r\n");
            Result = I2C_ERROR_TIMEOUT;
        }
    
        if (I2CError != I2C_ERROR_OK)
        {
            dbgprintf("transmit complete - error %d\n", I2CError);
            Result = I2CError;
        }
    }

    /* receive data */
    if (RxBytes > 0 && Result == I2C_ERROR_OK)
    {
        //dbgprintf("receiving %d bytes\r\n", RxBytes);
        
        // start the read
        if (TWID_Read(&twid, Address, 0, 0, (unsigned char *)RxBuf, RxBytes, &asynctwi))
            Result = I2C_ERROR_BUSY;
        
        // wait for completion
        if (xSemaphoreTake(I2CDone, I2C_TIMEOUT) != pdTRUE)
        {
            dbgprintf("can't get I2CDone in I2C_Transfer\r\n");
            Result = I2C_ERROR_TIMEOUT;
        }

        if (I2CError != I2C_ERROR_OK)
        {
            dbgprintf("receive complete - error %d\n", I2CError);
            Result = I2CError;
        }
    }

    /* release our lock on the hardware */
    xSemaphoreGive(I2CLock);
    //dbgprintf("finished I2C_Transfer\r\n");
    
    return Result;
}


/* 
 * NAME:        I2C_Send_Command
 * ACTION:      Send a command only 
 * PARAMETERS:  unsigned char Address - the I2C address to use
 *              unsigned char Command - the command to send
 * RETURNS:     I2C_ERROR_OK on success, I2C_ERROR_* on error
 */
 
int I2C_Send_Command(unsigned char Address, unsigned char Command)
{
    return I2C_Transfer(Address, &Command, sizeof(Command), NULL, 0);
}


/* interrupt service routine */
void I2C_IsrWrapper(void)
{
	/* Save the context of the interrupted task. */
	portSAVE_CONTEXT();

	/* Call the handler */
    TWID_Handler(&twid);
	AT91C_BASE_AIC->AIC_EOICR = 0;

	/* Restore the context of whichever task is going to run next. */
	portRESTORE_CONTEXT();
}


/* 
 * NAME:        I2C_Callback
 * ACTION:      React to the completion of a TWI operation
 * PARAMETERS:  AsyncTwi *async - information on what happened
 */

void I2C_Callback(void *param)
{
    portCHAR TaskWokenByPost = pdFALSE;
    AsyncTwi *async = (AsyncTwi *)param; 

    /* handle errors */
    if (async->status == TWID_ERROR_BUSY)
        I2CError = I2C_ERROR_BUSY;
    else
        I2CError = I2C_ERROR_OK;
        
    /* return control to the caller */
    TaskWokenByPost = xSemaphoreGiveFromISR(I2CDone, TaskWokenByPost);
    
    /* do a task switch if necessary */
    if (TaskWokenByPost)
        portYIELD_FROM_ISR();
}
